// AvenirSQL核心模块
const fs = require('fs');
const nthfile = require('nth-file');
const path = require('path');
const { SUCCESS, roots, WHITE_SPACE } = require('../tools/constants');
const { readIndex, countLine, overWrite, EOL, delQuotation, toLog, lockLevel } = require('./common/common');
const Btree = require('sorted-btree').default;
const libcu = require('libcu');
class Core {
    constructor() {

    }

    //判断该用户是否有权限
    checkAuth(database, sign) {
        if (roots[database]) {
            if (!sign || !conn.isRoot(sign)) {
                throw ('PERMISSION_DENIED');
            }
        }
        return true;
    }

    //获取数据库设置文件
    async getSet(notNodis) {
        let set = notNodis ? null : await nodis.get("dbSet", null);
        toLog("Nodis数据库配置的缓存为 ", set);
        if (set) {
            toLog("从缓存中获取dbSet");
            return set;
        }
        let setPath = ini.name.rootSet;
        set = fs.readFileSync(path.join(ini.main.path, setPath));
        set = JSON.parse(set);
        await nodis.put("dbSet", null, set);
        return set;
    }

    //写数据库配置文件
    async writeSet(data) {
        let setPath = path.join(ini.main.path, ini.name.rootSet);
        fs.writeFileSync(setPath, JSON.stringify(data));
        await nodis.put('dbSet', null, data);
    }

    async getTableDefine(tableName, tableFile, dbName, notNodis) {
        let res = notNodis ? null : await nodis.get('tableDefine', tableName, dbName);
        toLog("Nodis 关于表" + tableName + "的缓存为", res);
        if (!res) {
            res = await readIndex(0, tableFile);
            res = JSON.parse(res.toString());
            await nodis.put('tableDefine', tableName, res, dbName);
        }
        return res;
    }

    async getHashIndex(tableName, hashFile, dbName, notNodis) {
        let res = notNodis ? null : await nodis.get('hash', tableName, dbName);
        toLog("Nodis 关于表" + tableName + "的hash索引缓存为", res);
        if (!res) {
            res = fs.readFileSync(hashFile);
            res = JSON.parse(res);
            await nodis.put('hash', tableName, res, dbName);
        }
        return res;
    }

    async getFileArrCache(tableName, tableFile, dbName, notNodis) {
        let res = notNodis ? null : await nodis.get('tableData', tableName, dbName);
        toLog("Nodis 关于表" + tableName + "的行数据缓存为", res);
        if (!res) {
            res = nthfile.getFileArr(tableFile);
            //掐头去尾
            res.splice(0, 1);
            res.splice(res.length - 1, 1);
            await nodis.put('tableData', tableName, res, dbName);
        }
        return res;
    }

    //传表结构获取其中的主键和列数量
    async getTableKeyAndNum(tableDefine, tableName, dbName, notNodis) {
        let res = notNodis ? null : await nodis.get('tableDetail', tableName, dbName);
        toLog("Nodis 关于表" + tableName + "的主键和列缓存为", res);
        if (!res) {
            let key = null;
            let columns = tableDefine.table;
            let num = 0;
            for (let k in columns) {
                if (columns[k].key) {
                    key = k;
                }
                num++;
            }
            res = {
                key,
                num,
            };
            await nodis.put('tableDetail', tableName, res, dbName);

        }
        return res;
    }

    async getBPTreeFromCache(bptFile, tableName, dbName, notNodis) {
        let res = notNodis ? null : await nodis.get('bpTree', tableName, dbName);
        toLog("Nodis 关于表" + tableName + "的B+树索引缓存为", res);
        if (!res) {
            res = fs.readFileSync(bptFile);
            res = JSON.parse(res);
            await nodis.put('bpTree', tableName, res, dbName);
        }
        return res;
    }


    //传数据库名字 获取数据库和表文件名
    getFileNames(database, tableName, haveTable) {
        let dbPath = path.join(ini.main.path, database);
        if (!fs.existsSync(dbPath)) {
            throw ('DATABASE_NOT_FOUND');
        }
        let tableFile = path.join(dbPath, tableName + ini.name.table);
        //有传haveTable就代表期望表是存在的，否则期望表不存在
        if (haveTable) {
            if (!fs.existsSync(tableFile)) {
                throw ('TABLE_NOT_FOUND');
            }
        } else {
            if (fs.existsSync(tableFile)) {
                throw ('TABLE_EXIST');
            }
        }

        let hashFile = path.join(dbPath, tableName + ini.name.hash);
        let bptFile = path.join(dbPath, tableName + ini.name.index);
        return {
            dbPath,
            tableFile,
            hashFile,
            bptFile,
        }
    }

    //创建数据库 暂时不考虑存储页等问题
    async createDb(dbName) {
        if (!dbName) {
            throw ('INVALID_NAME');
        }
        let dbPath = ini.main.path;

        let dir = path.join(dbPath, dbName);
        if (fs.existsSync(dir)) {
            throw ('DATABASE_EXIST');
        } else {
            fs.mkdirSync(dir, { recursive: true });        //这样会递归创建目录
            this.initDb(dbName);
            dbName === 'User' || this.changeDefaultDb(dbName);
            await nodis.clearTableCache(['dbSet'], null, dbName); //tableDefine可以不清除
            if (ini.db.updateCache == 1) {
                await this.refreshCache(['dbSet'], dbName);
            }
            throw (SUCCESS);
        }
    }

    //初始化创建数据库设置文件 注意这里的dbName已经是全路径
    initDb(dbName) {
        let fr = ini.main.path;

        let fileName = path.join(path.join(fr, dbName), dbName + ini.name.dbSet);
        fs.writeFileSync(fileName, '{}');
    }

    //修改默认数据库
    async changeDefaultDb(dbName) {
        let fileName = path.join(ini.main.path, ini.name.rootSet);
        if (!fs.existsSync(fileName)) {
            throw ('FILE_NOT_EXIST');
        }
        let data = JSON.parse(fs.readFileSync(fileName).toString());
        data.defaultDb = dbName;
        fs.writeFileSync(fileName, JSON.stringify(data));
        await nodis.clearTableCache(['dbSet'], null, dbName); //tableDefine可以不清除
        if (ini.db.updateCache == 1) {
            await this.refreshCache(['dbSet'], dbName);
        }
    }


    //切换数据库 改写ini.name.rootSet文件下的defaultDB字段
    async useDB(dbName) {
        toLog("选择切换数据库至 ", dbName);
        if (!dbName) {
            throw ('INVALID_NAME');
        }
        await this.changeDefaultDb(dbName);
        throw (SUCCESS);
    }

    // 异步建表 以后可以加锁
    // 20201209 原则上sql解析器没有报错的sql，就尽可能完成建表操作
    async createTable(sql, sign) {

        //读数据库配置文件 选择数据库 数据库配置文件不可能是空的
        let set = await this.getSet();
        toLog('数据库配置文件为 ', set);
        let database = sql.name.db || set.defaultDb;
        if (!database) {
            throw ('DATABASE_NOT_FOUND');
        }
        this.checkAuth(database, sign);
        let tableName = sql.name.table;
        let tableOptions = sql.tableOptions;
        let columns = sql.columns;  // array

        //判断列是否超出上限
        if (columns.length > ini.db.maxcolumns) {
            throw ('TOO_MANY_COLUMNS');
        }

        let partitionOptions = sql.partitionOptions;
        let params = sql.params;    // array
        let paths = this.getFileNames(database, tableName, false);
        let dbPath = paths.dbPath;
        let table = paths.tableFile;
        let index = paths.bptFile;
        let hash = paths.hashFile;


        let first = {
            tableOptions,
            partitionOptions,
            params,
        };
        let t = {};
        let keyText = null;
        let ifKey = false;
        let whatskey = null;
        //解析建表语句中的主键和not语句
        for (let i = 0; i < columns.length; i++) {
            t[columns[i].name] = {};
            t[columns[i].name].type = columns[i].type.type;
            t[columns[i].name].len = columns[i].type.args ? columns[i].type.args[0] : '';
            let key = false, primary = false, not = false, notObj = null, index = false;
            for (let j = 0; j < columns[i].ext.length; j++) {
                if (columns[i].ext[j] === 'key') {
                    whatskey = columns[i].ext[j].value;
                    key = true;
                } else if (columns[i].ext[j] === 'primary') {
                    primary = true;
                } else if (columns[i].ext[j] === 'not') {
                    not = true;
                } else if (typeof columns[i].ext[j] == 'object') {
                    notObj = columns[i].ext[j].type === 'null' ? 'null' :
                        columns[i].ext[j].value;
                } else if (columns[i].ext[j] === 'index') {
                    index = true;
                }
            }
            t[columns[i].name].locate = i;
            t[columns[i].name].key = key && primary;
            // 判断是否有重复的主键
            if (key && primary) {
                keyText = columns[i].name;
                if (!ifKey) {
                    ifKey = true;
                } else {
                    throw ('ONLY_ONE_KEY');
                }
            }
            t[columns[i].name].not = (not && notObj) ? notObj : null;
            t[columns[i].name].index = index;
        }
        //初始化B+树 生成一颗空树
        fs.writeFileSync(index, JSON.stringify(this.initBpTree(whatskey)));

        // 判断是否没有主键
        if (!ifKey) {
            throw ('LACK_OF_PRIMARY_KEY');
        }

        first.table = t;
        let hashText = {};
        hashText[keyText] = {};
        hashText = JSON.stringify(hashText);
        fs.writeFileSync(hash, hashText);

        first = JSON.stringify(first);
        first += EOL;
        toLog("表结构为 ", first);
        fs.writeFileSync(table, first);
        toLog("建表成功");
        throw (SUCCESS);
    }

    //删除表功能 
    async dropTable(tableName, sign) {
        let array = tableName.split('.');
        let db = null;

        if (array.length === 1) {
            tableName = array[0];
        } else if (array.length === 2) {
            tableName = array[1];
            db = array[0];
        } else {
            throw ('SQL_PARSE_ERROR');
        }

        toLog("需要删除的表为 ", tableName);
        let set = await this.getSet();
        toLog('数据库配置文件为 ', set);
        let database = db || set.defaultDb;
        let dbPath = path.join(ini.main.path, database);
        if (!fs.existsSync(dbPath)) {
            throw ('DATABASE_NOT_FOUND');
        }
        let paths = this.getFileNames(database, tableName, true);

        let hashFile = paths.hashFile;
        let bptFile = paths.bptFile;
        let tableFile = paths.tableFile;
        if (!fs.existsSync(hashFile) || !fs.existsSync(bptFile) || !fs.existsSync(tableFile)) {
            throw ("TABLE_NOT_FOUND");
        }
        //把要删除的内容丢数组里
        let deletes = [hashFile, bptFile, tableFile];
        toLog("deletes = ", deletes);
        await lock.getLock(database, tableName, 0, sign, null, null);

        try {
            let files = await libcu.cf.walkFolder(dbPath);
            for (let i = 0; i < files.fileList.length; i++) {
                //判断是否在要删除的数组内
                if (libcu.tools.contains(deletes, files.fileList[i])) {
                    if (fs.existsSync(files.fileList[i])) {
                        //删除文件用unlinkSync rmSync会报找不到这个函数
                        fs.unlinkSync(files.fileList[i], { force: true });
                        toLog("删除了 ", files.fileList[i]);
                    } else {
                        throw ('TABLE_NOT_FOUND');
                    }
                } else {
                    toLog("跳过了 ", files.fileList[i]);
                }
            }
            throw (SUCCESS);
        } catch (error) {
            await lock.getLock(database, tableName, 1, sign, null, null);
            throw (error);
        }
    }

    //20201224 删库功能
    async dropDatabase(database) {
        toLog("需要删除的数据库为 ", database);
        let dbPath = path.join(ini.main.path, database);
        if (!fs.existsSync(dbPath)) {
            throw ('DATABASE_NOT_FOUND');
        }
        fs.rmdirSync(dbPath, { recursive: true });
        //遍历目录 赋值一个默认的数据库
        let walks = await libcu.cf.walkFolder(ini.main.path);
        console.log(walks);
        let dbs = walks.dirList;
        let set = await this.getSet();
        set.defaultDb = '';
        for (let i = 0; i < dbs.length; i++) {
            let dbName = path.basename(dbs[i]);
            if (dbName != ini.db.user) {
                set.defaultDb = dbName;
                break;
            }
        }
        toLog("set = ", set);
        await this.writeSet(set);
        throw (SUCCESS);
    }

    //插入表
    async insert(sql, sign) {
        let set = await this.getSet();
        toLog('数据库配置文件为 ', set);
        let database = sql.db || set.defaultDb;
        let table = sql.columns;              //插入语句的表结构
        let values = sql.values[0].value;            //插入语句的数据
        let tableName = sql.table;
        //列和数据不匹配
        if (table.length != values.length) {
            throw ('COLUMN_NOT_MATCH')
        }
        //列重复
        if (_.uniq(table).length !== table.length) {
            throw ('COLUMN_REPEAT');
        }
        let paths = this.getFileNames(database, tableName, true);

        let hashFile = paths.hashFile;
        let bptFile = paths.bptFile;
        let tableFile = paths.tableFile;
        let tableDefine = await this.getTableDefine(tableName, tableFile, database);
        let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, database);
        toLog("tableDefine = ", tableDefine);
        //表的列定义
        let tablecolumn = tableDefine.table;

        //生成一个要写入数据库的对象
        let data = {};
        //主键
        let tableKey = null;
        for (let key in tablecolumn) {
            if (tablecolumn[key].key == true) {
                tableKey = key;
            }
            data[key] = {
                value: null,
            }
        }
        let columns = sql.columns;
        for (let i = 0; i < columns.length; i++) {
            //注意这两个都是对象 只不过用了数据下标的调用方式
            if (!tablecolumn[columns[i]] || !data[columns[i]]) {
                throw ('COLUMN_NOT_FOUND');
            } else {
                //这里不需要判断列是否存在了
                data[columns[i]].value = values[i].value;
            }
        }
        // toLog("预定义的入库数据为 ", data);
        let keyValue = data[tableKey].value;
        await lock.getLock(database, tableName, 0, sign, null, null);
        try {
            let { dataStr } = await this.createLine(tableDefine, data);
            let hashIndex = await this.getHashIndex(tableName, hashFile, database);
            await this.hashLine(tableFile, 'insert', tableKey, keyValue, hashFile, hashIndex);
            fs.appendFileSync(tableFile, dataStr);

            //以下代码实现将B+树从文件中还原 并重构节点
            let btree = await this.restoreBpTree(bptFile, tableName, database, tableDetail);

            let simpleLine = this.columnParseSimple(tableDefine, dataStr, tableDetail.num);
            simpleLine[tableDetail.key] = delQuotation(simpleLine[tableDetail.key]);
            let bptKey = {
                [tableDetail.key]: simpleLine[tableDetail.key],
            }
            btree.set(bptKey, simpleLine);
            fs.writeFileSync(bptFile, JSON.stringify(btree));
            btree = undefined;  //gc

            //处理完之后再更新B+树索引      可以考虑从缓存读取然后更新数据 后期再更新
            await nodis.clearTableCache(['tableData', 'hash', 'bpTree'], tableName, database); //tableDefine可以不清除
            if (ini.db.updateCache == 1) {
                await this.refreshCache(['tableData', 'hash', 'bpTree'], database, tableName, tableFile, hashFile, bptFile);
            }
            throw (SUCCESS);
        } catch (error) {
            toLog(error);
            await lock.getLock(database, tableName, 1, sign, null, null);
            throw (error);
        }

    }


    //生成哈希索引 告诉数据库数据该如何存储
    async hashLine(tableFile, type, key, value, hashFile, hashIndex) {

        if (!hashIndex[key]) {
            hashIndex[key] = {};
        }

        if (hashIndex[key][value]) {
            throw ('KEY_EXIST');
        }

        if (type === 'insert') {
            let count = countLine(tableFile);
            let temp = hashIndex[key];
            temp[value] = count;

        }

        fs.writeFileSync(hashFile, JSON.stringify(hashIndex));
    }

    //传入key 生成一个B+树结构
    initBpTree(key) {
        let btree = new Btree(undefined, (a, b) => {
            if (a[key] > b[key]) {
                return 1;
            } else if (a[key] < b[key]) {
                return -1;
            } else {
                return a[key] - b[key];
            }
        });
        //20201227 这里初始化B+树拿到的数据居然是上一次生成的，what the fuck man
        btree._root.keys = [];
        btree._root.values = [],
            btree._root.isShared = true,
            btree._size = 0;
        btree._maxNodeSize = 32;

        return btree;
    }

    //从头创建B+树索引 传入表名 B+树文件名 生成B+树数据结构 写入文件 
    async createBpTree(bptFile, tableFile, tableName, dbName) {
        try {
            let tableDefine = await this.getTableDefine(tableName, tableFile, dbName);
            let tableData = await this.getFileArrCache(tableName, tableFile, dbName);

            toLog("createBpTree的表结构信息为 ", tableData);
            let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, dbName);
            let key = tableDetail.key;
            //先初始化      
            let btree = this.initBpTree(key);
            for (let i = 0; i < tableData.length; i++) {
                let data = this.columnParseSimple(tableDefine, tableData[i], tableDetail.num);
                data[key] = delQuotation(data[key]);
                let bptKey = {
                    key: data[key],
                }
                btree.set(bptKey, data);
            }
            fs.writeFileSync(bptFile, JSON.stringify(btree));
            btree = undefined;
        } catch (error) {
            toLog(error);
        }

    }

    // 删除表
    async delete(sql, sign) {
        toLog('sql = ', sql);
        toLog('sql.from = ', sql.from);
        let set = await this.getSet();
        //先解析目标数据表
        let database = sql.from[0].db;
        let tableName = sql.from[0].table;
        let asText = sql.as;

        database || (database = set.defaultDb);
        let paths = this.getFileNames(database, tableName, true);
        this.checkAuth(database, sign);
        let tableFile = paths.tableFile;
        let hashFile = paths.hashFile;
        let bptFile = paths.bptFile;

        await lock.getLock(database, tableName, 0, sign, null, null);
        try {
            let tableDefine = await this.getTableDefine(tableName, tableFile, database);
            let tablecolumn = tableDefine.table;
            let where = {};
            //有内容再解析
            if (sql.where) {
                where = await this.parseWhere({}, sql.where);
            }
            toLog("where = ", where);
            if (JSON.stringify(where) === '{}') {
                toLog("清全表");

                let first = await readIndex(0, tableFile);
                first += EOL;
                fs.writeFileSync(tableFile, first);
                fs.writeFileSync(hashFile, '{}' + EOL);
                //不用写B+树文件 后面通过函数自动生成了
            } else {
                //注意只有不是清全表才能拿左右子句  否则直接报错
                toLog("清某部分数据");
                //组合sql语句 多个column
                let deleteColumn = where;
                let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, database);
                let key = tableDetail.key;
                let num = tableDetail.num;
                let hasKey = false;         //这次的操作是否有主键
                //先判断是不是有不存在的列
                for (let key in deleteColumn) {
                    if (!tablecolumn[key]) {
                        throw ('COLUMN_NOT_FOUND');
                    }
                    //判断是否有主键
                    if (tablecolumn[key].key) {
                        hasKey = true;
                    }
                }
                toLog('hasKey = ', hasKey);
                if (deleteColumn[key].operator == '=') {
                    //走哈希索引 因为命中了主键 
                    toLog("通过哈希索引来删除表");
                    let hashIndex = await this.getHashIndex(tableName, hashFile);
                    let getIndex = deleteColumn[key].value;
                    toLog('hashIndex[key] = ', hashIndex[key]);
                    toLog("hashIndex = ", hashIndex);
                    //20201227 先判断这张表是否为空 增加!hashIndex[key]的判断
                    if (!hashIndex[key] || !hashIndex[key][getIndex]) {
                        //没有这个元素
                        toLog("主键未命中");
                        throw (SUCCESS);
                    }
                    toLog('hashIndex[key][getIndex] = ', hashIndex[key][getIndex]);
                    let line = nthfile.read(tableFile, hashIndex[key][getIndex]);
                    let res = await this.checkColumnAndData(line, deleteColumn, tableDefine, num);
                    toLog('判断的结果为 ', res);
                    if (res) {
                        //写文件 改库
                        nthfile.delete(tableFile, hashIndex[key][getIndex]);
                        delete hashIndex[key][getIndex];
                        fs.writeFileSync(hashFile, JSON.stringify(hashIndex));
                    } else {
                        throw (SUCCESS);
                    }
                } else {
                    //主键是范围查找
                    toLog('全表扫描来删除表');
                    let fileArr = await this.getFileArrCache(tableName,tableFile,database);
                    let deletes = [];
                    for (let i = 0; i < fileArr.length; i++) {
                        if (!fileArr[i]) {
                            break;
                        }
                        let judge = await this.checkColumnAndData(fileArr[i], deleteColumn, tableDefine, num);
                        toLog('判断这行数据是否要被删除的结果 为 ', judge);
                        if (judge) {
                            deletes.push(i);
                        }

                    }
                    toLog('deletes = ', deletes);
                    if (deletes.length > 0) {
                        nthfile.deleteLines(tableFile, deletes, fileArr);
                    } else {
                        throw (SUCCESS);
                    }

                }
            }
            //改为先清缓存再抛成功
            await nodis.clearTableCache(['tableData', 'hash', 'bpTree'], tableName, database); //tableDefine可以不清除
            if (ini.db.updateCache == 1) {
                await this.refreshCache(['tableData', 'hash', 'bpTree'], database, tableName, tableFile, hashFile, bptFile);
            }
            await this.createBpTree(bptFile, tableFile, tableName, database);
            await this.createHash(tableFile, hashFile, tableDefine, tableName, database);
            throw (SUCCESS);
        } catch (error) {
            //最后不解锁会死锁
            toLog(error);
            await lock.getLock(database, tableName, 1, sign, null, null);
            throw (error);
        }
    }

    //更新表
    async update(sql, sign) {
        toLog("数据库更新语句为 ", sql);
        let database = sql.db;
        let tableName = sql.table;
        let asText = sql.as;
        let set = await this.getSet();
        database || (database = set.defaultDb);

        let paths = this.getFileNames(database, tableName, true);
        let tableFile = paths.tableFile;
        let hashFile = paths.hashFile;
        let bptFile = paths.bptFile;
        await lock.getLock(database, tableName, 0, sign, null, null);
        try {
            let tableDefine = await this.getTableDefine(tableName, tableFile, database);
            let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, database);
            let key = tableDetail.key;
            let num = tableDetail.num;
            let where = {};
            //有内容再解析
            if (sql.where) {
                where = await this.parseWhere({}, sql.where);
            }
            toLog("where = ", where);
            toLog("sql.set = ", sql.set);
            let setColumn = this.parseUpdateSet(sql.set);
            if (where[key] && where[key].operator == '=') {
                toLog("更新语句走哈希索引");
                let hashIndex = await this.getHashIndex(tableName, hashFile, database);
                let getIndex = where[key].value;
                let index = hashIndex[key][getIndex];
                if (!index) {
                    toLog("哈希索引未命中");
                    throw (SUCCESS);
                }
                //如果更新涉及到主键  则需要判断更新后是否冲突 反之则不可判断 必然会“冲突”
                if (hashIndex[key] && hashIndex[key][setColumn[key].value]) {
                    throw ('KEY_EXIST');
                }
                delete hashIndex[key][getIndex];            //把老的索引删除
                let wholeFileArr = nthfile.getFileArr(tableFile);
                let lineText = wholeFileArr[index];
                let hit = await this.checkColumnAndData(lineText, where, tableDefine, num, true);
                toLog("hit = ", hit);
                if (!hit) {
                    throw (SUCCESS);
                }
                //到这里则说明行需要被修改
                let changed = await this.createUpdateLine(tableDefine, hit.parsedColumn, setColumn);
                let writeData = changed.dataStr;
                let checkData = changed.data;

                if (setColumn[key]) {

                    hashIndex[key][checkData[key].value] = index;
                    wholeFileArr[index] = writeData;
                    nthfile.writeFile(tableFile, wholeFileArr);
                    fs.writeFileSync(hashFile, JSON.stringify(hashIndex));

                } else {
                    wholeFileArr[index] = writeData;
                    nthfile.writeFile(tableFile, wholeFileArr);
                }

            } else {
                toLog("全表搜索");
                let wholeFileArr = await this.getFileArrCache(tableName, tableFile, database);
                let hitFlag = false;
                for (let i = 0; i < wholeFileArr.length; i++) {
                    let hit = await this.checkColumnAndData(wholeFileArr[i], where, tableDefine, num, true);
                    if (hit) {
                        let temp = await this.createUpdateLine(tableDefine, hit.parsedColumn, setColumn);
                        wholeFileArr[i] = temp.dataStr;
                        hitFlag = true;
                    } else {
                        continue;
                    }
                }

                if (hitFlag) {
                    nthfile.writeFile(tableFile, wholeFileArr);
                    await this.createHash(tableFile, hashFile, tableDefine, tableName, database);
                    await this.createBpTree(bptFile, tableFile, tableName, database);
                    throw (SUCCESS);
                }
            }
            //结束
            await nodis.clearTableCache(['tableData', 'hash', 'bpTree'], tableName, database); //tableDefine可以不清除

            //如果配置了缓存策略则立即刷新缓存
            if (ini.db.updateCache == 1) {
                toLog("立即刷新缓存");
                await this.refreshCache(['tableData', 'hash', 'bpTree'], database, tableName, tableFile, hashFile, bptFile);
            }
            await this.createBpTree(bptFile, tableFile, tableName, database);
            // await nodis.show();
            throw (SUCCESS);
        } catch (error) {
            toLog(error);
            await lock.getLock(database, tableName, 1, sign, null, null);
            throw (error);
        }
    }

    //解析update语句中的set语句
    parseUpdateSet(set) {
        let res = {};
        for (let i = 0; i < set.length; i++) {
            res[set[i].column] = set[i].value.value;
        }
        return res;
    }



    //后期实现B+树搜索
    async select(sql, sign, auth) {
        toLog("传进来的sql 为 ", sql);
        //规避目前还不支持的SQL
        if (sql.groupby != null || sql.distinct != null) {
            throw ('SQL_NOT_SUPPORT');
        }
        let orderby = sql.orderby;
        let limit = sql.limit;
        let limitResult = -1;
        if (limit && limit.length) {
            for (let i = 0; i < limit.length; i++) {
                if (limit[i].type != 'number') {
                    throw ('SQL_NOT_SUPPORT');
                } else {
                    limitResult = limitResult > limit[i].value ? limitResult : limit[i].value;
                }
            }
        }
        limit = undefined;          //直接gc
        toLog("limitResult = ", limitResult);

        let orderByResult = [];     //要排序的列
        let orderRule = [];         //升序降序
        if (orderby && orderby.length) {
            for (let i = 0; i < orderby.length; i++) {
                if (orderby[i].expr.type != 'column_ref') {
                    throw ('SQL_NOT_SUPPORT');
                }
                orderByResult.push(orderby[i].expr.column);
                if (orderby[i].type == 'ASC' || orderby[i].type == 'asc') {
                    orderRule.push('asc');
                } else if (orderby[i].type == 'DESC' || orderby[i].type == 'desc') {
                    orderRule.push('desc');
                } else {
                    throw ('SQL_NOT_SUPPORT');
                }
            }
        }
        toLog("orderByResult = ", orderByResult);
        toLog("orderRule = ", orderRule);
        let set = await this.getSet();

        toLog('数据库配置文件为 ', set);
        let database = sql.from[0].db || set.defaultDb;
        toLog("sign = ", sign);
        //false为数据库内部的查询 不进行权限校验
        if (auth !== false) {
            this.checkAuth(database, sign);
        }
        let tableName = sql.from[0].table;
        let paths = this.getFileNames(database, tableName, true);
        let tableFile = paths.tableFile;
        let hashFile = paths.hashFile;
        let bptFile = paths.bptFile;
        await lock.getLock(database, tableName, 0, sign, null, null);

        try {
            let tableDefine = await this.getTableDefine(tableName, tableFile, database);
            let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, database);
            toLog("tableDefine = ", tableDefine);
            let key = tableDetail.key;
            let num = tableDetail.num;

            let selectColumns = sql.columns;        //是一个数组
            toLog("selectColumns = ", selectColumns);
            //遇到通配符就转成数组 
            if (selectColumns == '*') {
                selectColumns = [];
                for (let key in tableDefine.table) {
                    let selectTemp = {};
                    selectTemp.as = null;       //as必然是null
                    selectTemp.expr = {
                        type: 'column_ref',
                        table: '',
                        column: key
                    }
                    selectColumns.push(selectTemp);
                }
            }

            let where = {};
            //有内容再解析
            let res = [];
            if (sql.where) {
                where = await this.parseWhere({}, sql.where);
            }
            toLog("where = ", where);
            if (where[key] && where[key].operator == '=') {
                toLog("select操作命中主键");
                let hashIndex = await this.getHashIndex(tableName, hashFile, database);
                let index = hashIndex[key][where[key].value];
                toLog("要查询的记录在 ", index);
                //主键不存在直接代表数据不存在
                if (!index && index !== 0) {
                    throw ({ code: SUCCESS, data: res });
                }
                let lineText = await readIndex(index, tableFile);
                res = await this.combineSelectedData(selectColumns, [lineText], where, tableDefine, num);
            } else if (where[key] && where[key].operator != '=') {
                //尝试用上B+树
                let btree = await this.restoreBpTree(bptFile, tableName, database, tableDetail);
                let low = {}, high = {};
                low[key] = null;
                high[key] = "max";      //默认赋值max 不然啥都查不到
                if (where[key].operator == '>') {
                    low[key] = where[key].value - 1;
                }
                if (where[key].operator == '>=') {
                    low[key] = where[key].value;
                }
                if (where[key].operator == '<') {
                    high[key] = where[key].value;
                }
                if (where[key].operator == '<=') {
                    high[key] = where[key].value + 1;
                }
                let selectBpt = btree.getRange(low, high);
                toLog("selectBpt = ", selectBpt);
                let cleaned = await this.convertBpTreeResult(selectBpt);
                res = await this.combineSelectedData(selectColumns, cleaned, where, tableDefine, num, false);
            } else {
                toLog("全表搜索");

                let arr = await this.getFileArrCache(tableName, tableFile, database);
                res = await this.combineSelectedData(selectColumns, arr, where, tableDefine, num);
            }

            //     //暂时只支持count(*)吧 
            if (orderByResult.length || orderRule.length) {
                res = _.sortBy(res, orderByResult, orderRule);
            }
            if (limitResult != -1) {
                res.length = res.length < limitResult ? res.length : limitResult;       //limit的长度两者取最小
            }
            throw ({ code: SUCCESS, data: res });
        } catch (error) {
            toLog(error);
            await lock.getLock(database, tableName, 1, sign, null, null);
            throw (error);
        }
    }

    //删除B+树查询结果里的多余数据 转换成想要的结果
    async convertBpTreeResult(arr) {
        let res = [];
        for (let i = 0; i < arr.length; i++) {
            let temp = {};
            for (let key in arr[i][1]) {
                temp[key] = {
                    value: arr[i][1][key],
                };
            }
            res.push(temp);
        }
        return res;
    }

    //组装查询到的数据  lineArr是数组
    async combineSelectedData(selectColumns, lineArr, where, tableDefine, num, needParse) {
        let res = [];
        let count = 0;
        for (let k = 0; k < lineArr.length; k++) {
            if (!lineArr[k]) {
                continue;
            }
            let result = await this.checkColumnAndData(lineArr[k], where, tableDefine, num, true, needParse);
            if (!result) {
                continue;
            }
            count++;
            let parsedColumn = result.parsedColumn;
            let temp = {};      //入数组的元素 组成 { key : value , key1:value} 的形式
            for (let i = 0; i < selectColumns.length; i++) {
                let left = selectColumns[i].as || selectColumns[i].expr.column || selectColumns[i].expr.name;     //有as就取as，否则取column原生字段

                //先判断有没有COUNT！
                if (selectColumns[i].expr.type === 'aggr_func' && selectColumns[i].expr.name == 'COUNT' || selectColumns[i].expr.name == 'count') {
                    res = [];
                    temp[left] = count;
                    break;
                }

                //先处理查询的列重复的问题
                if (temp[selectColumns[i].expr.column]) {
                    throw ("COLUMN_REPEAT");
                }
                //再处理列不在表中的问题
                if (!parsedColumn[selectColumns[i].expr.column]) {
                    toLog('parsedColumn = ', parsedColumn);
                    toLog("selectColumns[i].expr.column = ", selectColumns[i].expr.column);
                    throw ("COLUMN_NOT_FOUND");
                }
                temp[left] = parsedColumn[selectColumns[i].expr.column].value;
            }
            res.push(temp);
        }
        return res;
    }

    //根据先前写入的数据和表结构 对该行数据进行解析
    columnParse(tableDefine, column, columnNum) {
        let columns = tableDefine.table;
        let arr = column.split(WHITE_SPACE);
        let ifNull = libcu.tools.padZero(parseInt(arr[0], 16), columnNum);
        let i = 0;    //解析用

        //然后按照行定义将数据解析
        for (let key in columns) {
            let locate = columns[key].locate;
            //当这个下标有数据的时候 下标往前移动一次 注意需要跳过第一个压缩字节
            if (ifNull[locate] == 1) {
                columns[key].value = arr[i + 1];
                i++;
            } else {
                columns[key].value = null;
            }
        }
        return columns;
    }

    //根据先前写入的数据和表结构 对该行数据进行解析 然后只返回{key:value}
    columnParseSimple(tableDefine, column, columnNum) {
        let columns = tableDefine.table;
        column = column.replace(EOL, "");
        let arr = column.split(WHITE_SPACE);
        let ifNull = libcu.tools.padZero(parseInt(arr[0], 16), columnNum);
        let i = 0;    //解析用
        let temp = {};
        //然后按照行定义将数据解析
        for (let key in columns) {
            let locate = columns[key].locate;
            //当这个下标有数据的时候 下标往前移动一次 注意需要跳过第一个压缩字节
            if (ifNull[locate] == 1) {
                temp[key] = arr[i + 1];
                i++;
            } else {
                temp[key] = null;
            }
        }
        return temp;
    }


    //根据表的数据重构哈希索引 结构 {key : {value : index }}
    async createHash(tableFile, hashFile, tableDefine, tableName, dbName) {
        let tableDetail = await this.getTableKeyAndNum(tableDefine, tableName, dbName);
        let key = tableDetail.key;
        let num = tableDetail.num;
        let hash = {};
        hash[key] = {};     //先初始化哈希索引
        let arr = await this.getFileArrCache(tableName, tableFile, dbName);
        toLog("arr = ", arr);
        //20201227 getFileArrCache 已经将数据掐头去尾了，所以这里必须要从0开始 Holy crap
        for (let i = 0; i < arr.length; i++) {
            toLog(" 创建哈希拿到的数据为 ", arr[i]);
            if (!arr[i]) {
                break;
            }
            let parseResult = this.columnParse(tableDefine, arr[i], num);

            hash[key][parseResult[key].value] = i;
        }
        fs.writeFileSync(hashFile, JSON.stringify(hash));
    }


    //本函数实现生成默认的B+树结构 并从文件内还原B+树
    async restoreBpTree(bptFile, tableName, database, tableDetail) {
        //以下代码实现将B+树从文件中还原 并添加新的节点
        let btree = this.initBpTree(tableDetail.key);
        let btreeTemp = await this.getBPTreeFromCache(bptFile, tableName, database);
        btree._root.keys = btreeTemp ? btreeTemp._root.keys : btree._root.keys;
        btree._root.values = btreeTemp ? btreeTemp._root.values : btree._root.values;
        btree._size = btreeTemp ? btreeTemp._size : btree._size;

        return btree;
    }


    //传原生的行数据和sql里面的列数据，返回这个行数据是否被命中 return true/false 
    async checkColumnAndData(rawColumn, sqlColumn, tableDefine, columnNum, ifObject, dontParse) {
        //20201223 增加参数 判断需不需要解析源数据
        let parsedColumn = null;
        if (dontParse == true || dontParse == undefined) {
            parsedColumn = this.columnParse(tableDefine, rawColumn, columnNum);
            console.log(parsedColumn);
        } else {
            parsedColumn = rawColumn;
        }
        //不输出日志了 会造成IO操作频繁 需要的结构如下
        // {
        //     b: { operator: '>', value: '40000' },
        //     c: { operator: '>', value: '20000' }
        // }
        // toLog("checkColumnAndData sqlColumn = ",sqlColumn);
        //判断sql里面的列在不在解析的行数据里面 注意不要搞反了
        for (let key in sqlColumn) {
            if (!parsedColumn[key]) {

                throw ('COLUMN_NOT_FOUND');
            }
            let operator = sqlColumn[key].operator;
            let value = sqlColumn[key].value;
            let tableValue = parsedColumn[key].value;

            if (!value || !tableValue) {
                return false;
            }
            if (operator != '=' && (isNaN(value) || isNaN(tableValue))) {
                return false;       //比大小的话还不是数字那就不用比了
            }
            if (operator == '=') {
                //因为isNaN不会判断空字符串和null
                if (tableValue != value) {
                    return false;
                }
            } else if (operator == '>') {
                if (tableValue <= value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            } else if (operator == '<') {
                if (tableValue >= value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            }
            // 20201223 增加对 >= <= <> !=的支持
            else if (operator == '<=') {
                if (tableValue > value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            } else if (operator == '>=') {
                if (tableValue < value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            } else if (operator == '<>') {
                if (tableValue == value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            } else if (operator == '!=') {
                if (tableValue == value || isNaN(tableValue) || isNaN(value)) {
                    return false;
                }
            }
            else {
                toLog('checkColumnAndData 进入了未知的case ,tableValue = ', tableValue, " value = ", value, ' operator = ', operator);
                throw ("SQL_NOT_SUPPORT");
            }
        }
        //校验全通过了就返回true
        if (ifObject) {
            return {
                result: true,
                parsedColumn,
            }
        } else {
            return true;
        }
    }


    //用于update语句写入行 parsedColumn 是已被解析的原数据,sqlColumn是sql语句要修改的列

    // 预定义的入库数据为  {
    //     a: { value: '93812' },
    //     b: { value: '32312' },
    //     c: { value: '01927' },
    //     d: { value: '93795' },
    //     e: { value: '41139' }
    //   }
    async createUpdateLine(tableDefine, parsedColumn, sqlColumn) {
        toLog("createUpdateLine sqlColumn = ", sqlColumn);
        for (let key in sqlColumn) {
            if (!parsedColumn[key]) {
                throw ('COLUMN_NOT_FOUND');
            }
            //把要修改的值赋上
            parsedColumn[key].value = sqlColumn[key];
        }
        return await this.createLine(tableDefine, parsedColumn, true);
    }

    async createLine(tableDefine, data, noEOL) {
        let tablecolumn = tableDefine.table;
        let ifNull = '';        //数据行第一行 表示是否有空元素
        let dataStr = '';       //数据行压缩元素       
        for (let key in data) {

            //列非空检查失败
            if (!data[key].value && (tablecolumn[key].not == 'null')) {
                throw ('COLUMN_NOT_NULL');
            }
            //列not check失败
            if ((data[key].value == tablecolumn[key].not) && tablecolumn[key].not !== null) {
                throw ('COLUMN_NOT_CHECK');
            }

            //压缩元素规则，不为空就是1，空则为0
            if (data[key].value != null) {
                ifNull += '1';
                dataStr += WHITE_SPACE + data[key].value;
            } else {
                ifNull += '0';
            }
        }

        //压缩成16进制数
        ifNull = parseInt(ifNull).toString(16);

        //有些情况要换行符 有些不要
        if (noEOL) {
            dataStr = ifNull + dataStr
        } else {
            dataStr = ifNull + dataStr + EOL;
        }
        return {
            dataStr,
            data
        };
    }

    //递归解析多个where 和 and时的数据
    async parseWhere(result, where) {
        //用递归
        if (where && (where.operator == 'AND' || where.operator == 'and')) {
            await this.parseWhere(result, where.left);
            await this.parseWhere(result, where.right);
            return result;
        }
        else {
            if (result[where.left.column]) {
                throw ('COLUMN_REPEAT');
            }
            result[where.left.column] = {
                operator: where.operator,
                value: where.right.value,
            }
            return result;
        }
    }

    //根据传入的数组 刷新缓存
    async refreshCache(arr, dbName, tableName, tableFile, hashFile, bptFile) {
        toLog("开始刷新缓存 处理的对象为 ", arr);
        for (let i = 0; i < arr.length; i++) {
            //感觉后期可以用promiseAll优化
            if (arr[i] == 'tableDetail') {
                await this.getTableKeyAndNum(tableName, tableFile, dbName);
            } else if (arr[i] == 'hash') {
                await this.getHashIndex(tableName, hashFile, dbName);
            } else if (arr[i] == 'tableData') {
                await this.getFileArrCache(tableName, tableFile, dbName);
            } else if (arr[i] == 'tableDefine') {
                await this.getTableDefine(tableName, tableFile, dbName);
            } else if (arr[i] == 'bpTree') {
                await this.getBPTreeFromCache(bptFile, tableName, dbName);
            } else {
                throw ("UNKNOWN_ERROR");
            }
        }
        return;
    }

    //展示目前所有的数据库名
    async showDatabase() {
        let dbPath = ini.main.path;
        let files = await libcu.cf.walkFolder(dbPath);
        let dirs = files.dirList;
        for(let i=0;i<dirs.length;i++) {
            dirs[i] = path.basename(dirs[i]);
        }
        throw({code:SUCCESS,data:dirs});
    }

    //展示目前所有的表名
    async showTable(database) {
        let dbSet = await this.getSet();
        database = database || dbSet.defaultDb;
        let dbPath = path.join(ini.main.path, database);
        if(!fs.existsSync(dbPath)) {
            throw ("DATABASE_NOT_FOUND");
        }
        let all = await libcu.cf.walkFolder(dbPath);
        let files = all.fileList;
        let res = [];
        let tableName = ini.name.table;
        tableName = tableName.replace('.',"");
        for(let i=0;i<files.length;i++) {
            let fileRawName = path.basename(files[i]);
            let arr = fileRawName.split('.');
            if(arr[1] == tableName) {
                res.push(arr[0]);
            }
        }
        throw({code : SUCCESS,data:res});
    }
}

module.exports = Core;